﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace HazeCore.Persistence
{
    public abstract class PersistableList<T> : IList<T>, OfflineListHelper.IOfflineListOperator<T>
    {        
        #region Nested types

        private class OrderedList<TItem> : List<TItem>, OfflineListHelper.IOrderedList<TItem>
        {
        }

        private class ChangedBlock : OfflineListHelper.AbstractChangeBlock<T>
        {
            #region Properties

            public override OfflineListHelper.IOrderedList<T> Items
            {
                get { return _items; }
            }

            #endregion

            #region Fields

            private readonly OfflineListHelper.IOrderedList<T> _items = new OrderedList<T>();

            #endregion
        }

        #endregion

        #region Properties

        #region IList members

        #region IOfflineListOperator<T> Members

        public OfflineListHelper.IOrderedList<OfflineListHelper.IChangeBlock<T>> Changes
        {
            get { return _changes; }
        }

        #endregion

        public T this[int index]
        {
            get { return this.GetItemAt(index); }
            set { this.SetItemAt(index, value); }
        }

        #endregion

        public T this[long index]
        {
            get { return this.GetItemAt(index); }
            set { this.SetItemAt(index, value); }
        }


        public ITypeSequencer<T> TypeSequencer { get; set; }

        public IBinaryPersister Persister { get; private set; }

        public int MaxNumber { get; set; }

        #endregion

        #region Constructors

        protected PersistableList(IBinaryPersister persister, int typicalSize)
        {
            Persister = persister;
            _typicalSize = typicalSize;
        }

        #endregion

        #region Methods

        #region IList members

        public int IndexOf(T item)
        {
            throw new NotSupportedException();
        }

        public void Insert(int index, T item)
        {
            OfflineListHelper.InsertRange(this, index, new []{item});
        }

        public void RemoveAt(int index)
        {
            OfflineListHelper.RemoveRange(this, index, 1);
        }

        public void Add(T item)
        {
            Insert(Count, item);
        }

        public void Clear()
        {
            RemoveRange(0, Count);
        }

        public bool Contains(T item)
        {
            throw new NotSupportedException();
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            throw new NotSupportedException();
        }

        public int Count
        {
            get
            {
                if (LCount < int.MaxValue) return (int) LCount;
                return -1;
            }
        }

        public long LCount { get; private set; }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(T item)
        {
            throw new NotSupportedException();
        }

        public IEnumerator<T> GetEnumerator()
        {
            for (long i = 0; i < TypeSequencer.Count; i++)
            {
                yield return this[i];
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region IOfflineListOperator<T> Members

        public T LoadAt(long index)
        {
            var idxChangeRight = this.GetChangeBlock(index);
            idxChangeRight = -idxChangeRight - 1;
            var idxChangeLeft = idxChangeRight - 1;

            var physicalLeft = index - _typicalSize/2;
            var physicalRight = physicalLeft + _typicalSize;

            var changeLeft = idxChangeLeft >= 0? Changes[idxChangeLeft] : null;
            var changeRight = idxChangeRight < Changes.Count ? Changes[idxChangeRight] : null;
            var changeLeftEnd = changeLeft != null ? changeLeft.End : 0;
            var changeRightStart = changeRight != null ? changeRight.End : TypeSequencer.Count;
            var changeLeftVirtualEnd = changeLeft != null ? changeLeft.VirtualEnd : 0;
            var lenMid = changeRightStart - changeLeftEnd;

            if (lenMid < _typicalSize)
            {
                physicalLeft = changeLeftEnd;
                physicalRight = changeRightStart;
            }
            else
            {
                if (physicalLeft < changeLeftEnd) physicalLeft = changeLeftEnd;
                if (physicalRight > changeRightStart) physicalRight = changeRightStart;
            }

            var loaded = TypeSequencer.RetrieveRange(physicalLeft, (int)(physicalRight - physicalLeft));
            var newChange = CreateChangeBlock();
            newChange.Start = physicalLeft;
            newChange.End = physicalRight;
            newChange.VirtualStart = physicalLeft - changeLeftEnd + changeLeftVirtualEnd;
            this.AddBlock(idxChangeRight, newChange);

            var offset = (int)(index - physicalLeft);
            return loaded[offset];
        }

        public OfflineListHelper.IChangeBlock<T> CreateChangeBlock()
        {
            return new ChangedBlock();
        }

        #endregion

        public void InsertRange(int index, IList<T> items)
        {
            OfflineListHelper.InsertRange(this, index, items);
            LCount += items.Count;
        }

        public void RemoveRange(int index, int count)
        {
            OfflineListHelper.RemoveRange(this, index, count);
            LCount -= count;
        }

        /// <summary>
        ///  Searches for the specified item from the specified range in this list where items
        ///  are kept in ascending order
        /// </summary>
        /// <param name="target">The item to find</param>
        /// <param name="start">The starting point of the range</param>
        /// <param name="count">The length of the range</param>
        /// <param name="compare">The method that compares the item</param>
        /// <returns></returns>
        public long BinarySearch(T target, long start, long count, Comparison<T> compare)
        {
            long low = start, high = start + count;
            while (low < high)
            {
                var mid = (low + high)/2;
                var valMid = this[mid];
                var comp = compare(target, valMid);
                if (comp < 0)
                {
                    high = mid;
                }
                else if (comp > 0)
                {
                    low = mid + 1;
                }
                else
                {
                    return mid;
                }
            }
            return -low - 1;
        }

        public void Writeback()
        {
            for (var i = Changes.Count - 1; i >= 0; i--)
            {
                var change = Changes[i];
                if (!change.IsDirty) continue;
                TypeSequencer.ReplaceRange(change.Start, change.End - change.Start, change.Items);
            }
        }

        #endregion

        #region Fields

        private readonly OfflineListHelper.IOrderedList<OfflineListHelper.IChangeBlock<T>> _changes 
            = new OrderedList<OfflineListHelper.IChangeBlock<T>>();

        private readonly int _typicalSize;

        #endregion
    }
}
